package com.magenta.gis.lira;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.magenta.gis.lira.xml.StreamQuery;
import com.magenta.reflection.util.Reflector;

class XmlMapper {
	
	private final static Logger LOG = LoggerFactory.getLogger(XmlMapper.class);

	private final static String XML_TAG_PATTERN = "(?!XML)\\p{L}[\\p{L}0-9-]*";
	private final static String JAVA_CLASS_NAME_PATTERN = "\\p{L}[\\p{L}0-9_\\$\\.]*";//SIMPLE VERSION
	static final Pattern NEW_OBJECT_PATTERN = Pattern.compile("new (" + JAVA_CLASS_NAME_PATTERN + ")\\((.*\\))", Pattern.CASE_INSENSITIVE);
	static final Pattern NEXT_XML_TAG_PATTERN = Pattern.compile("(" + XML_TAG_PATTERN + ").*");
	
	public static Object firstPattern(String queryPattern, InputStream in) {
		//PARSE QUERY
		SelectTree sTree = new SelectTree(queryPattern);
		Matcher newObjectMatcher;
		if ( sTree.isParsed() && (newObjectMatcher = NEW_OBJECT_PATTERN.matcher(sTree.baseExpr)).matches() ) {
			String className = newObjectMatcher.group(1);
			String group2 = newObjectMatcher.group(2);
			String listXmlTags = group2.substring(0, group2.length() - 1);
			String fromTree = sTree.getFromExpr();
			try {
				Class<?> classOfObject = Thread.currentThread().getContextClassLoader().loadClass(className);
				List<String> xmlTagsList = getXmlTagsList(listXmlTags);
				List<String> xmlPath = getXmlPath(fromTree);
				Map<Integer, Object> result = new StreamQuery(in, xmlPath, xmlTagsList).getFirstResult();
				//TRY TO FIND CONSTRUCTOR
				int argumentsCount = xmlTagsList.size();
				Constructor<?> cons = Reflector.findConstructor(classOfObject, argumentsCount, result);
				if ( cons != null ) {
					if ( !cons.isAccessible() ) {//TRY CHANGE ACCESIBILITY
						cons.setAccessible(true);
					}
					return cons.newInstance(getArguments(argumentsCount, result));
				} else {
					LOG.warn("CONSTRUCTOR WITH [%s] ARGUMENTS NOT FOUND FOR CLASS [%s]", argumentsCount, classOfObject.getName());
					return null;
				}
			}
			catch ( ClassNotFoundException e ) {
				LOG.info("ERROR WHILE LOADING CLASS", e);
			} catch (IllegalArgumentException e) {
				LOG.info("ERROR WHILE CREATING NEW INSTANCE OF A CLASS", e);
			} catch (InstantiationException e) {
				LOG.info("ERROR WHILE CREATING NEW INSTANCE OF A CLASS", e);
			} catch (IllegalAccessException e) {
				LOG.info("ERROR WHILE CREATING NEW INSTANCE OF A CLASS", e);
			} catch (InvocationTargetException e) {
				LOG.info("ERROR WHILE CREATING NEW INSTANCE OF A CLASS", e);
			} catch (XMLStreamException e) {
				LOG.info("ERROR WHILE PROCESSING QUERY", e);
			} catch (FactoryConfigurationError e) {
				LOG.info("ERROR WHILE PROCESSING QUERY", e);
			}
		}
		LOG.warn("PARSE QUERY [%s] WITHOUT SUCCESS!", queryPattern);
		return null;
	}

	private static Object[] getArguments(int argumentsCount,
			Map<Integer, Object> result) {
		Object[] arguments = new Object[argumentsCount];
		for ( int i = 0, len = arguments.length; i < len; i++ ) {
			arguments[i] = result.get(i);
		}
		return arguments;
	}

	static List<String> getXmlPath(String pathExpr) {
		List<String> xmlPath = new ArrayList<String>();
		String path = pathExpr.substring(1);
		while ( !path.isEmpty() ) {
			Matcher nextXmlTagMatcher;
			if ( (nextXmlTagMatcher = NEXT_XML_TAG_PATTERN.matcher(path)).matches() ) {
				String xmlTag = nextXmlTagMatcher.group(1);
				xmlPath.add(xmlTag);
				int slashPos = path.indexOf('/', xmlTag.length());
				if ( slashPos >= 0 ) {
					path = path.substring(slashPos + 1, path.length()).trim();
					continue;
				}
			}
			path = "";
		}
		return xmlPath;
	}

	static List<String> getXmlTagsList(String listXmlTags) {
		List<String> xmlTags = new ArrayList<String>();
		String tagList = listXmlTags;
		while ( !tagList.isEmpty() ) {
			Matcher nextXmlTagMatcher;
			if ( (nextXmlTagMatcher = NEXT_XML_TAG_PATTERN.matcher(tagList)).matches() ) {
				String xmlTag = nextXmlTagMatcher.group(1);
				xmlTags.add(xmlTag);
				int commaPos = tagList.indexOf(',', xmlTag.length());
				if ( commaPos >= 0 ) {
					tagList = tagList.substring(commaPos + 1, tagList.length()).trim();
					continue;
				}
			}
			tagList = "";
		}
		return xmlTags;
	}
	
	static class SelectTree {
		private String baseExpr;
		private String fromExpr;
		private boolean parsed;

		private final static Pattern QUERY1 = Pattern.compile("select\\s+(.*\\s+)from(\\s+.*)", Pattern.CASE_INSENSITIVE);
		
		public SelectTree(String query) {
			Matcher queryMatcher;
			if ( (queryMatcher = QUERY1.matcher(query)).matches() ) {
				baseExpr = queryMatcher.group(1).trim();
				fromExpr = queryMatcher.group(2).trim();
				parsed = true;
			}
		}

		public String getBaseExpr() {
			return baseExpr;
		}

		public String getFromExpr() {
			return fromExpr;
		}

		public boolean isParsed() {
			return parsed;
		}
		
		
	}

}
